package org.zs.miracle.service.auth.impl;

import cn.dev33.satoken.stp.SaLoginConfig;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import jakarta.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.zs.miracle.common.constant.RoleConstant;
import org.zs.miracle.common.exception.UserFriendlyException;
import org.zs.miracle.common.util.ModelMapperUtil;
import org.zs.miracle.common.util.RequestUtil;
import org.zs.miracle.common.util.UserAgentInfo;
import org.zs.miracle.common.util.tree.ITreeNode;
import org.zs.miracle.common.util.tree.TreeUtil;
import org.zs.miracle.entity.system.ResourcesEntity;
import org.zs.miracle.entity.system.RoleResourcesEntity;
import org.zs.miracle.entity.system.UserEntity;
import org.zs.miracle.entity.system.UserRoleEntity;
import org.zs.miracle.mapper.ResourcesMapper;
import org.zs.miracle.mapper.RoleMapper;
import org.zs.miracle.mapper.RoleResourcesMapper;
import org.zs.miracle.mapper.UserMapper;
import org.zs.miracle.mapper.UserRoleMapper;
import org.zs.miracle.service.auth.IAuthService;
import org.zs.miracle.service.auth.model.dto.AuthenticationUserDetailVO;
import org.zs.miracle.service.auth.model.dto.LoginInputDTO;
import org.zs.miracle.service.auth.model.dto.LoginOutputDTO;
import org.zs.miracle.service.resources.model.vo.ResourcesTreeVo;
import org.zs.miracle.service.resources.model.vo.ResourcesVo;
import org.zs.miracle.service.role.model.vo.RoleVo;

@AllArgsConstructor
@Service
public class AuthServiceImpl implements IAuthService {
  
  ResourcesMapper resourcesRepository;
  UserMapper userRepository;
  RoleMapper roleRepository;
  UserRoleMapper userRoleRepository;
  RoleResourcesMapper roleResourcesRepository;
  
  @Override
  public List<ResourcesEntity> queryPermissionsList(Long userId) {
    // 如果有超级管理员角色拥有全部权限
    if (StpUtil.getRoleList().contains(RoleConstant.SUPER_ADMIN_CODE)) {
      return resourcesRepository.selectList(null);
    }
    // 用户角色id集合
    List<Long> userRoleIds = userRoleRepository.selectList(
            Wrappers.<UserRoleEntity>lambdaQuery().eq(UserRoleEntity::getUserId, userId))
        .stream()
        .map(UserRoleEntity::getRoleId)
        .toList();
    
    if (userRoleIds.isEmpty()) {
      return new ArrayList<>();
    }
    
    // 角色的权限id集合
    List<Long> roleResourcesList = roleResourcesRepository.selectList(
            Wrappers.<RoleResourcesEntity>lambdaQuery().in(RoleResourcesEntity::getRoleId, userRoleIds))
        .stream()
        .map(RoleResourcesEntity::getResourcesId)
        .toList();
    
    if (roleResourcesList.isEmpty()) {
      return new ArrayList<>();
    }
    return resourcesRepository.selectList(
        Wrappers.<ResourcesEntity>lambdaQuery().in(ResourcesEntity::getId, roleResourcesList));
  }
  
  @Override
  public List<ResourcesTreeVo> queryPermissionsTree() {
    
    // 当前用户id
    long currentUserId = StpUtil.getLoginIdAsLong();
    // 获取用户权限列表
    List<ResourcesTreeVo> resourcesVos =
        ModelMapperUtil.mapList(this.queryPermissionsList(currentUserId), ResourcesTreeVo.class);
    
    // 转换ITreeNode List
    List<ITreeNode<Long>> treeNodeList = new ArrayList<>(resourcesVos.size());
    treeNodeList.addAll(resourcesVos);
    
    List<ITreeNode<Long>> tree = TreeUtil.listToTree(treeNodeList);
    return ModelMapperUtil.mapList(tree, ResourcesTreeVo.class);
  }
  
  @Override
  public String captcha() {
    return null;
  }
  
  @Override
  public LoginOutputDTO login(LoginInputDTO inputDTO, HttpServletRequest request) {
    
    // 获取用户信息
    UserEntity loginUser = userRepository.selectOne(
        Wrappers.<UserEntity>lambdaQuery().eq(UserEntity::getAccount, inputDTO.getAccount()));
    
    // 判断账号有效性
    if (null == loginUser) {
      throw new UserFriendlyException("账号不存在", 430);
    }
    
    // 输入的密码
    String inputPwd =
        DigestUtil.sha256Hex(inputDTO.getPassword() + loginUser.getSalt(), CharsetUtil.UTF_8);
    
    // 比对密码
    if (!StrUtil.equals(inputPwd, loginUser.getPassword())) {
      throw new UserFriendlyException("账号或密码错误", 420);
    }
    
    // 解析User-Agent的信息
    String header = request.getHeader("User-Agent");
    UserAgentInfo userAgentInfo = RequestUtil.parseUserAgent(header);
    // sa-token 登录 并设置扩展参数, 通过StpUtil.getExtra("user");获取扩展参数
    StpUtil.login(loginUser.getId(), SaLoginConfig
        .setDevice("PC")
        .setExtra("device", userAgentInfo.getOs() + " " + userAgentInfo.getBrowser())
        .setExtra("id", loginUser.getId())
        .setExtra("username", loginUser.getUsername())
        .setExtra("account", loginUser.getAccount())
        .setExtra("phone", loginUser.getPhone())
        .setExtra("user", loginUser)
    );
    
    // 获取token信息
    SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
    
    return LoginOutputDTO
        .builder()
        .accessToken(tokenInfo.getTokenValue())
        .id(loginUser.getId())
        .account(loginUser.getAccount())
        .avatarUrl(loginUser.getAvatarUrl())
        .username(loginUser.getUsername())
        .gender(loginUser.getGender())
        .phone(loginUser.getPhone())
        .build();
  }
  
  @Override
  public void logout() {
    StpUtil.logout(StpUtil.getLoginId(), "PC");
  }
  
  @Override
  public AuthenticationUserDetailVO authenticationUserDetail() {
    // 当前用户Id
    long currentUserId = StpUtil.getLoginIdAsLong();
    // 查询当前用户
    AuthenticationUserDetailVO
        output = ModelMapperUtil.map(userRepository.selectById(currentUserId),
        AuthenticationUserDetailVO.class);
    // 用户角色列表
    List<RoleVo> roles = roleRepository.queryUserRolesByUserId(currentUserId);
    // 资源权限列表
    List<ResourcesVo> permissions =
        ModelMapperUtil.mapList(this.queryPermissionsList(currentUserId), ResourcesVo.class);
    // 组装角色
    output.setRoles(roles);
    // 组装权限
    output.setPermissions(permissions);
    return output;
  }
}